<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>第六章：Memory模块</title>
    <link rel="stylesheet" href="../shared_styles.css">
</head>
<body>
    <div class="container">
        <h1>第六章：Memory模块</h1>

        <p>在构建聊天机器人或任何需要进行多轮对话的AI应用时，Memory（记忆）模块至关重要。它使得应用程序能够记住之前的交互内容，从而在后续的对话中保持上下文连贯性，提供更智能、更个性化的用户体验。Langchain提供了灵活的Memory机制来处理对话历史。</p>

        <h2 id="6-1">6.1 Memory模块简介</h2>
        <p>Memory模块的核心功能是存储和检索对话过程中的消息。没有Memory，LLM在每次调用时都是无状态的，无法“记住”之前的对话内容。通过引入Memory，我们可以将历史消息传递给LLM，使其能够基于完整的对话上下文生成回应。</p>
        <p>Langchain的Memory组件可以用于Chains和Agents中，以赋予它们记忆能力。最新的Langchain版本推荐使用 <code>RunnableWithMessageHistory</code> 类来为可运行对象（Runnables）添加消息历史记录功能，这是一种更现代和灵活的方式。</p>

        <h2 id="6-2">6.2 Memory的核心概念</h2>
        <p>在使用Langchain的Memory功能，特别是 <code>RunnableWithMessageHistory</code> 时，有几个核心概念需要理解：</p>
        <ul>
            <li><strong>Session ID (会话ID)</strong>: 这是一个唯一的标识符，用于区分不同的对话线程。通过为每个对话分配一个Session ID，系统可以同时管理多个独立的对话历史。</li>
            <li><strong>BaseChatMessageHistory</strong>: 这是一个基类，定义了加载和保存消息对象的接口。具体的Memory存储后端（如内存、文件、数据库等）都需要实现这个接口。</li>
            <li><strong>get_session_history (获取会话历史的函数)</strong>: 这是一个由用户提供的函数，它接收一个Session ID作为参数，并返回一个与该Session ID关联的 <code>BaseChatMessageHistory</code> 对象实例。这个函数是连接 <code>RunnableWithMessageHistory</code> 和实际消息存储的关键。</li>
        </ul>

        <h3>RunnableWithMessageHistory 详解</h3>
        <p>在构建聊天机器人时，将对话状态传入和传出链是至关重要的。<code>RunnableWithMessageHistory</code> 类允许我们为某些类型的链添加消息历史。它包装另一个可运行对象，并管理其聊天消息历史。具体来说，它在将之前的消息传递给可运行对象之前加载对话中的先前消息，并在调用可运行对象后将生成的响应保存为消息。该类还通过使用 <code>session_id</code> 保存每个对话来支持多个对话 - 然后在调用可运行对象时期望在配置中传递 <code>session_id</code>，并使用它查找相关的对话历史。</p>

        <p>实践中，这看起来像：</p>
        <pre><code class="language-python">
from langchain_core.runnables.history import RunnableWithMessageHistory

# runnable: 你想要包装的底层可运行对象 (例如一个LLMChain或者一个ChatModel)
# get_session_history: 一个函数，接收 session_id 并返回一个 BaseChatMessageHistory 对象

# with_message_history = RunnableWithMessageHistory(
#     runnable,  
#     get_session_history,  
#     input_messages_key="input", # 指定输入消息在字典中的键名 (如果输入是字典)
#     history_messages_key="history", # 指定历史消息在字典中的键名 (如果输入是字典)
#     # ... 其他可能需要的参数，用于对齐Runnable的输入/输出与Memory对象
# )

# result = with_message_history.invoke(
#     {"input": "你好，我是小明。"}, # 与底层runnable匹配的输入
#     config={"configurable": {"session_id": "abc123"}} # 指定session_id
# )
        </code></pre>

        <p>为了正确设置这一点，有两个主要事项需要考虑：</p>
        <ol>
            <li><strong>如何存储和加载消息？</strong> (这在上面的示例中是 <code>get_session_history</code> 函数的职责) </li>
            <li><strong>你正在包装的底层 Runnable 是什么，它的输入/输出是什么？</strong> (这决定了 <code>runnable</code> 参数以及你可能需要传递给 <code>RunnableWithMessageHistory</code> 的其他参数，如 <code>input_messages_key</code>, <code>history_messages_key</code>, <code>output_messages_key</code> 等，以对齐输入/输出)。</li>
        </ol>

        <h4>1. 如何存储和加载消息</h4>
        <p>这其中一个关键部分是存储和加载消息。在构造 <code>RunnableWithMessageHistory</code> 时，你需要传入一个 <code>get_session_history</code> 函数。该函数应接受一个 <code>session_id</code> 并返回一个 <code>BaseChatMessageHistory</code> 对象。</p>
        <p>让我们创建一个 <code>get_session_history</code> 函数来用于这个示例。为了简单起见，我们将使用一个简单的SQLite作为消息存储后端。</p>
        <p>首先，确保你安装了必要的库：</p>
        <pre><code class="language-bash">
pip install langchain langchain-core langchain-community dashscope
        </code></pre>
        <p>然后，你可以定义 <code>get_session_history</code>：</p>
        <pre><code class="language-python">
import os
from langchain_community.chat_message_histories import SQLChatMessageHistory

# 如果 memory.db 文件已存在，可以先删除它以开始一个全新的历史记录
# 在Python脚本中，可以使用 os.remove("memory.db") 如果文件存在
# 或者在shell中执行: !rm memory.db (Jupyter Notebook/IPython) 或 del memory.db (Windows CMD)

def get_session_history(session_id: str) -> SQLChatMessageHistory:
    # 为每个session_id创建一个独立的SQLite数据库文件，或者使用同一个文件中的不同表
    # 这里我们使用同一个数据库文件，SQLChatMessageHistory内部会根据session_id管理记录
    return SQLChatMessageHistory(session_id, "sqlite:///memory.db")

# 示例：手动清理 (可选)
# if os.path.exists("memory.db"):
#     os.remove("memory.db")
#     print("已删除旧的 memory.db 文件。")
        </code></pre>
        <p>请查看 Langchain 的 <a href="https://python.langchain.com/docs/integrations/memory/" target="_blank">Memory集成页面</a>，了解使用其他存储后端（如Redis、Postgres等）实现的聊天消息历史。</p>

        <h4>2. 你想要包装的可运行对象是什么？</h4>
        <p><code>RunnableWithMessageHistory</code> 可以包装特定类型的可运行对象。具体来说，它可以用于任何输入为以下之一的可运行对象：</p>
        <ul>
            <li>一系列 <code>BaseMessage</code> 对象。</li>
            <li>一个字典，其中某个键的值是一系列 <code>BaseMessage</code> 对象。</li>
            <li>一个字典，其中一个键是最新的消息（作为字符串或 <code>BaseMessage</code> 序列），另一个键是历史消息。</li>
        </ul>
        <p>并且其输出为以下之一：</p>
        <ul>
            <li>可以被视为 <code>AIMessage</code> 内容的字符串。</li>
            <li>一个 <code>BaseMessage</code> 对象。</li>
            <li>一个字典，其中某个键的值是一个 <code>BaseMessage</code> 对象。</li>
        </ul>

        <h4>设置Qwen模型</h4>
        <p>我们将使用通义千问（Qwen）模型作为示例。首先，确保你已经设置了环境变量 <code>DASHSCOPE_API_KEY</code>。</p>
        <pre><code class="language-python">
import os
from langchain_community.chat_models import ChatTongyi
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory

# 设置您的通义千问API密钥
# os.environ["DASHSCOPE_API_KEY"] = "YOUR_DASHSCOPE_API_KEY" 

try:
    # 建议使用 qwen-turbo, qwen-plus, qwen-max, qwen-max-longcontext 等
    # 具体可用的模型请参考阿里云百炼平台文档
    llm = ChatTongyi(model_name="qwen-turbo") 
    print("通义千问 ChatTongyi LLM 初始化成功！")
except Exception as e:
    print(f"初始化通义千问模型失败，请检查API Key和网络连接: {e}")
    llm = None # 设置为None，以便后续代码可以安全检查

# 确保 get_session_history 函数已定义 (如上所示)
        </code></pre>

        <h4>示例1：消息输入，消息输出 (Message List Input, Message Output)</h4>
        <p>最简单的形式是直接为一个聊天模型（ChatModel）添加记忆。聊天模型接受一系列消息作为输入，并输出一条消息。这使得使用 <code>RunnableWithMessageHistory</code> 非常简单，不需要额外的配置。</p>
        <pre><code class="language-python">
if llm:
    runnable_with_history_simple = RunnableWithMessageHistory(
        llm, # 直接包装聊天模型
        get_session_history,
    )

    print("\n--- 示例1: 消息输入，消息输出 ---")
    print("会话 1:")
    response1 = runnable_with_history_simple.invoke(
        [HumanMessage(content="你好，我叫小明！")],
        config={"configurable": {"session_id": "session_1_simple"}},
    )
    print(f"AI: {response1.content}")

    response2 = runnable_with_history_simple.invoke(
        [HumanMessage(content="我叫什么名字？")],
        config={"configurable": {"session_id": "session_1_simple"}},
    )
    print(f"AI: {response2.content}")

    print("\n会话 2 (新的session_id):")
    response3 = runnable_with_history_simple.invoke(
        [HumanMessage(content="我叫什么名字？")],
        config={"configurable": {"session_id": "session_2_simple"}}, # 新的session_id
    )
    print(f"AI: {response3.content}")
else:
    print("LLM未能成功初始化，示例1无法运行。")
        </code></pre>
        <p><strong>注意：</strong> 在第一个会话中，模型应该能记住“小明”这个名字。在第二个会话中，由于使用了新的 <code>session_id</code>，模型将不记得之前的对话内容。</p>

        <h4>示例2：字典输入，消息输出 (Dictionary Input, Message Output)</h4>
        <p>通常我们会将提示模板（PromptTemplate）和LLM组合成一个链。提示模板的输入通常是一个字典。这种情况下，我们需要告诉 <code>RunnableWithMessageHistory</code>：</p>
        <ul>
            <li><code>input_messages_key</code>: 字典中哪个键对应的是用户最新的输入消息。</li>
            <li><code>history_messages_key</code>: 字典中哪个键应该用来填充历史消息。这通常对应于提示模板中的 <code>MessagesPlaceholder</code>。</li>
        </ul>
        <pre><code class="language-python">
if llm:
    prompt = ChatPromptTemplate.from_messages(
        [
            ("system", "你是一个乐于助人的助手，请用{language}回答，并尽量简洁，不超过30个词。"),
            MessagesPlaceholder(variable_name="history"), # 用于历史消息
            ("human", "{input}"), # 用户当前输入
        ]
    )
    runnable_prompt_llm = prompt | llm

    runnable_with_history_dict_input = RunnableWithMessageHistory(
        runnable_prompt_llm,
        get_session_history,
        input_messages_key="input",       # 告诉Runnable哪个输入键是用户消息
        history_messages_key="history",   # 告诉Runnable哪个键是历史消息占位符
    )

    print("\n--- 示例2: 字典输入，消息输出 ---")
    print("会话 3 (意大利语):")
    response4 = runnable_with_history_dict_input.invoke(
        {"language": "意大利语", "input": "你好，我叫小红！"},
        config={"configurable": {"session_id": "session_3_dict"}},
    )
    print(f"AI: {response4.content}")

    response5 = runnable_with_history_dict_input.invoke(
        {"language": "意大利语", "input": "我叫什么名字？"},
        config={"configurable": {"session_id": "session_3_dict"}},
    )
    print(f"AI: {response5.content}")

    print("\n会话 4 (意大利语, 新的session_id):")
    response6 = runnable_with_history_dict_input.invoke(
        {"language": "意大利语", "input": "我叫什么名字？"},
        config={"configurable": {"session_id": "session_4_dict_new"}},
    )
    print(f"AI: {response6.content}")
else:
    print("LLM未能成功初始化，示例2无法运行。")
        </code></pre>

        <h4>示例3：消息输入，字典输出 (Message List Input, Dictionary Output)</h4>
        <p>当你的链输出一个包含消息的字典时，你需要指定 <code>output_messages_key</code>。</p>
        <pre><code class="language-python">
from langchain_core.runnables import RunnableParallel

if llm:
    # 假设我们的链是这样的：输入是消息列表，输出是一个字典，其中一个键是AI的回复消息
    chain_dict_output = RunnableParallel({"output_message": llm})

    runnable_with_history_dict_output = RunnableWithMessageHistory(
        chain_dict_output,
        get_session_history,
        output_messages_key="output_message", # 指定输出字典中哪个键是AI消息
    )

    print("\n--- 示例3: 消息输入，字典输出 ---")
    print("会话 5:")
    response7_dict = runnable_with_history_dict_output.invoke(
        [HumanMessage(content="你好，我叫小华！")],
        config={"configurable": {"session_id": "session_5_dict_out"}},
    )
    # response7_dict 将是类似 {'output_message': AIMessage(content=...)} 的形式
    if isinstance(response7_dict, dict) and "output_message" in response7_dict:
        print(f"AI: {response7_dict['output_message'].content}")
    else:
        print(f"AI (raw): {response7_dict}")


    response8_dict = runnable_with_history_dict_output.invoke(
        [HumanMessage(content="我叫什么名字？")],
        config={"configurable": {"session_id": "session_5_dict_out"}},
    )
    if isinstance(response8_dict, dict) and "output_message" in response8_dict:
        print(f"AI: {response8_dict['output_message'].content}")
    else:
        print(f"AI (raw): {response8_dict}")
else:
    print("LLM未能成功初始化，示例3无法运行。")
        </code></pre>

        <h4>示例4：自定义历史工厂配置 (Custom History Factory Config)</h4>
        <p>你可以通过传递一组 <code>ConfigurableFieldSpec</code> 对象到 <code>history_factory_config</code> 参数来自定义用于跟踪消息历史的配置参数。例如，你可以使用 <code>user_id</code> 和 <code>conversation_id</code> 两个参数来共同确定一个会话。</p>
        <pre><code class="language-python">
from langchain_core.runnables import ConfigurableFieldSpec

if llm:
    # 假设我们使用与示例2相同的 prompt 和 llm
    # runnable_prompt_llm = prompt | llm (已在示例2中定义)

    def get_session_history_custom(user_id: str, conversation_id: str) -> SQLChatMessageHistory:
        # 将 user_id 和 conversation_id 组合成一个唯一的 session_id
        session_id = f"user_{user_id}_conv_{conversation_id}"
        return SQLChatMessageHistory(session_id, "sqlite:///memory_custom.db")

    # 清理旧的自定义数据库 (可选)
    # if os.path.exists("memory_custom.db"):
    #     os.remove("memory_custom.db")

    runnable_with_custom_history = RunnableWithMessageHistory(
        runnable_prompt_llm, # 使用示例2的 runnable
        get_session_history_custom,
        input_messages_key="input",
        history_messages_key="history",
        history_factory_config=[
            ConfigurableFieldSpec(
                id="user_id",
                annotation=str,
                name="User ID",
                description="用户的唯一标识符。",
                default="",
                is_shared=True,
            ),
            ConfigurableFieldSpec(
                id="conversation_id",
                annotation=str,
                name="Conversation ID",
                description="对话的唯一标识符。",
                default="",
                is_shared=True,
            ),
        ],
    )

    print("\n--- 示例4: 自定义历史工厂配置 ---")
    print("用户 'user123', 对话 'conv_A':")
    response9 = runnable_with_custom_history.invoke(
        {"language": "法语", "input": "你好，我叫小李。"},
        config={"configurable": {"user_id": "user123", "conversation_id": "conv_A"}},
    )
    print(f"AI: {response9.content}")

    response10 = runnable_with_custom_history.invoke(
        {"language": "法语", "input": "我叫什么名字？"},
        config={"configurable": {"user_id": "user123", "conversation_id": "conv_A"}},
    )
    print(f"AI: {response10.content}") # 应该记住名字

    print("\n用户 'user123', 对话 'conv_B' (新的对话ID):")
    response11 = runnable_with_custom_history.invoke(
        {"language": "法语", "input": "我叫什么名字？"},
        config={"configurable": {"user_id": "user123", "conversation_id": "conv_B"}},
    )
    print(f"AI: {response11.content}") # 不应该记住名字，因为是新的对话

    print("\n用户 'user789', 对话 'conv_A' (新的用户ID):")
    response12 = runnable_with_custom_history.invoke(
        {"language": "法语", "input": "我叫什么名字？"}, # 假设这个用户之前没有介绍过自己
        config={"configurable": {"user_id": "user789", "conversation_id": "conv_A"}},
    )
    print(f"AI: {response12.content}") # 不应该记住名字，因为是新的用户
else:
    print("LLM未能成功初始化，示例4无法运行。")
        </code></pre>
        <p><strong>注意：</strong> 在这种情况下，上下文是为相同的 <code>user_id</code> 和 <code>conversation_id</code> 组合保留的。一旦更改其中任何一个，就会开始一个新的聊天历史。</p>

        <h2 id="6-3">6.3 Langchain中其他类型的Memory (传统方式)</h2>
        <p>除了 <code>RunnableWithMessageHistory</code> 这种现代方式，Langchain也提供了多种传统的Memory类，它们通常与旧版的 <code>LLMChain</code> 或 <code>ConversationChain</code> 一起使用。虽然 <code>RunnableWithMessageHistory</code> 是推荐的方式，但了解这些传统的Memory类型也有助于理解Langchain的演进和处理不同场景。</p>
        <ul>
            <li><strong>ConversationBufferMemory (6.3.1)</strong>: 这是最简单的Memory类型。它将所有历史对话消息存储在一个缓冲区中，并在每次调用时将它们全部传递给LLM。
                <ul><li>优点：完整保留对话历史。</li>
                    <li>缺点：对于长对话，可能会很快超出LLM的上下文窗口限制，导致性能下降或错误。</li>
                </ul>
            </li>
            <li><strong>ConversationBufferWindowMemory (6.3.2)</strong>: 为了解决 <code>ConversationBufferMemory</code> 的上下文窗口问题，这个Memory类型只保留最近的K轮对话。
                <ul><li>优点：控制了上下文长度，避免溢出。</li>
                    <li>缺点：会丢失较早的对话信息。</li>
                </ul>
            </li>
            <li><strong>ConversationTokenBufferMemory (6.3.3)</strong>: 与 <code>ConversationBufferWindowMemory</code> 类似，但它不是根据对话轮数，而是根据消息的总Token数量来限制历史记录的长度。
                <ul><li>优点：更精确地控制Token数量，适应不同LLM的限制。</li>
                    <li>缺点：同样会丢失超出Token限制的早期信息。</li>
                </ul>
            </li>
            <li><strong>ConversationSummaryMemory (6.3.4)</strong>: 当对话变得很长时，这种Memory会使用一个LLM来动态地生成对话的摘要，并将摘要作为历史信息传递。
                <ul><li>优点：能够处理非常长的对话，同时保留关键信息。</li>
                    <li>缺点：生成摘要需要额外的LLM调用，增加了成本和延迟。摘要的质量也依赖于LLM。</li>
                </ul>
            </li>
            <li><strong>ConversationSummaryBufferMemory (6.3.5)</strong>: 这是 <code>ConversationBufferMemory</code> 和 <code>ConversationSummaryMemory</code> 的结合。它在内存中保留最近的对话消息，当消息数量超过一定限制时，将较早的消息转换为摘要。
                <ul><li>优点：平衡了信息完整性和上下文长度。</li>
                    <li>缺点：实现相对复杂，仍有摘要生成的开销。</li>
                </ul>
            </li>
            <li><strong>其他类型的Memory (6.3.6)</strong>: Langchain还支持更高级的Memory类型，如基于知识图谱的Memory（<code>ConversationKGMemory</code>）、实体Memory（<code>ConversationEntityMemory</code>）等，它们适用于更复杂的场景。此外，还有向量存储支持的Memory，如 <code>VectorStoreRetrieverMemory</code>，它可以将对话历史存储在向量数据库中，并检索相关的历史片段。</li>
        </ul>
        <p>这些传统Memory类通常通过 <code>ConversationChain</code> 的 <code>memory</code> 参数来使用。例如：</p>
        <pre><code class="language-python">
# 传统方式示例 (使用 ConversationChain 和 ConversationBufferMemory)
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

if llm: # 确保 llm (ChatTongyi 实例) 已定义
    # 注意：ConversationChain 通常期望一个 LLM 实例，而不是 ChatModel 实例。
    # 如果 llm 是 ChatTongyi (ChatModel)，直接用于 ConversationChain 可能不理想或需要适配。
    # 为简单起见，这里假设有一个基础的 LLM 实例可用，或者 ChatTongyi 能被 ConversationChain 接受。
    # 更好的做法是，如果使用 ChatModel，应坚持使用 LCEL 和 RunnableWithMessageHistory。

    # 为了演示传统方式，我们可能需要一个基础LLM包装器
    # from langchain_community.llms import Tongyi as TongyiLLM
    # base_llm = TongyiLLM(model_name="qwen-turbo")

    # memory_traditional = ConversationBufferMemory()
    # conversation_traditional = ConversationChain(
    #     llm=llm, # 或者 base_llm
    #     memory=memory_traditional,
    #     verbose=True
    # )
    # print("\n--- 传统Memory示例 (ConversationBufferMemory) ---")
    # print(conversation_traditional.predict(input="你好，我叫小王。"))
    # print(conversation_traditional.predict(input="我叫什么名字？"))
    pass # 现代方法 RunnableWithMessageHistory 是首选
else:
    print("LLM未能成功初始化，传统Memory示例无法完整演示。")
        </code></pre>
        <p><strong>重要提示：</strong> 对于新的Langchain项目，强烈建议优先使用 <code>RunnableWithMessageHistory</code> 来管理对话历史，因为它与Langchain表达式语言（LCEL）更好地集成，提供了更大的灵活性和可组合性。</p>

        <h2 id="6-4">6.4 在Chains和Agents中使用Memory</h2>
        <p>如上文大量示例所示，<code>RunnableWithMessageHistory</code> 是在现代Langchain应用中（基于LCEL构建的Chains）集成Memory的主要方式。</p>
        <ul>
            <li><strong>在Chains中使用 (6.4.1, 6.4.2)</strong>: 你可以将任何符合输入输出约定的Runnable（例如一个 <code>ChatPromptTemplate | ChatModel</code> 链）包装在 <code>RunnableWithMessageHistory</code> 中，从而赋予它记忆能力。这适用于简单的LLMChain以及更复杂的顺序链（SequentialChain）或自定义链，只要它们最终的输入输出符合要求。</li>
            <li><strong>在Agents中使用 (6.4.3)</strong>: 对于Agents，尤其是对话型Agent（如 <code>ConversationalAgent</code> 或使用 <code>AgentType.CONVERSATIONAL_REACT_DESCRIPTION</code> 初始化），Memory是其核心组成部分。通常，在初始化这类Agent时，你会直接传入一个Memory对象实例（例如 <code>ConversationBufferMemory</code>）。Agent的执行器（AgentExecutor）会负责在每次迭代中从Memory加载历史，并将新的交互存入Memory。
                <p>虽然 <code>RunnableWithMessageHistory</code> 主要针对Runnables，但Agent本身也可以被视为一种特殊的Runnable。更高级的Agent设计可能会利用类似 <code>RunnableWithMessageHistory</code> 的机制，或者Agent内部已经实现了对特定Memory对象的管理。</p>
            </li>
        </ul>

        <h2 id="6-5">6.5 Memory的最佳实践和注意事项</h2>
        <ul>
            <li><strong>选择合适的Memory类型/策略</strong>: 根据应用需求选择。短对话用Buffer，长对话考虑Window、TokenBuffer或Summary。<code>RunnableWithMessageHistory</code> 提供了灵活的后端存储选择。</li>
            <li><strong>管理上下文窗口</strong>: LLM的上下文窗口是有限的。过长的历史消息会导致截断或错误。使用限制长度的Memory类型（如Window, TokenBuffer）或摘要型Memory。</li>
            <li><strong>持久化</strong>: 对于生产应用，通常需要将对话历史持久化到数据库（如SQLite, PostgreSQL, Redis等），而不是仅存在于内存中。<code>SQLChatMessageHistory</code>, <code>RedisChatMessageHistory</code> 等就是为此设计的。</li>
            <li><strong>Session管理</strong>: 确保为每个独立的用户会话使用唯一的 <code>session_id</code>。</li>
            <li><strong>成本和延迟</strong>: 某些Memory类型（如摘要型）会引入额外的LLM调用，增加成本和延迟。</li>
            <li><strong>安全和隐私</strong>: 对话历史可能包含敏感信息。存储和处理这些数据时，务必遵守相关的安全和隐私法规。</li>
            <li><strong>与LCEL的集成</strong>: 优先使用 <code>RunnableWithMessageHistory</code>，因为它与Langchain表达式语言（LCEL）无缝集成，易于组合和扩展。</li>
        </ul>

        <h2 id="6-6">6.6 总结</h2>
        <p>Memory是构建智能对话应用不可或缺的一部分。Langchain通过 <code>RunnableWithMessageHistory</code> 和各种 <code>BaseChatMessageHistory</code> 实现，提供了强大而灵活的机制来管理对话状态。通过理解其核心概念、不同类型的Memory以及如何将其集成到Chains和Agents中，你可以构建出能够进行流畅、有上下文感知对话的AI应用。始终关注上下文长度、持久化和安全性等方面的最佳实践。</p>

        <div class="navigation">
            <a href="../langchain_tutorial_outline.html">返回目录</a>
            <a href="../chapter5/chapter5_agents.html">上一章：Agents模块</a>
            <a href="../chapter7/chapter7_indexes.html">下一章：Indexes模块</a>
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
    <script>hljs.highlightAll();</script>
</body>
</html>
